﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using log4net;
using System.Net.Sockets;
using System.IO;
using Repo7.BusinessLogic.Utils.Exceptions;
using Repo7.BusinessLogic.Utils;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace Repo7.BusinessLogic.Network.Commander
{
    internal class AsyncCommandTransferStateObject
    {
        #region Constants

        /// <summary>
        /// Size of receive buffer.
        /// </summary>
        public const int DefaultBufferSize = 1024;

        /// <summary>
        /// Default Max Message size = 1 megabyte
        /// </summary>
        public const int DefaultMaxMessageSize = 536870912;

        #endregion

        #region Public Properties

        /// <summary>
        /// Client  socket.
        /// </summary> 
        public Socket WorkSocket = null;
        
        /// <summary>
        /// Receive buffer.
        /// </summary>
        public byte[] Buffer;

        /// <summary>
        /// Message Length.
        /// </summary>
        public long? MessageLength { get; private set; }

        /// <summary>
        /// Bytes received.
        /// </summary>
        public long BytesReceived { get; set; }

        /// <summary>
        /// Maximum message size.
        /// </summary>
        public long MaxMessageSize { get; private set; }

        /// <summary>
        /// Received data string.
        /// </summary>
        public MemoryStream MemStream { get; private set; }
                
        #endregion

        #region Constructors
        
        /// <summary>
        /// Constructor received max message size
        /// </summary>
        public AsyncCommandTransferStateObject(int? maxMessageSize = null)
        {
            if (maxMessageSize.HasValue)
            {
                MaxMessageSize = maxMessageSize.Value;
            }
            else
            {
                MaxMessageSize = DefaultMaxMessageSize;
            }

            Buffer = new byte[DefaultBufferSize];
            MessageLength = null;
            BytesReceived = 0;
            MemStream = new MemoryStream();
        }

        /// <summary>
        /// Create State Object using Socket and action for message received
        /// </summary>
        /// <param name="workerSocket"></param>
        /// <param name="onMessageReceived"></param>
        /// <param name="maxMessageSize"></param>
        public AsyncCommandTransferStateObject(Socket workerSocket, int? maxMessageSize = null) : this(maxMessageSize)
        {
            WorkSocket = workerSocket;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Gets the first sizeof(int) bytes representing the size of the message and sets the MessageLenght property. 
        /// </summary>
        public void ReadMessageLength()
        {
            byte[] sizeArray = Buffer.Take(sizeof(long)).ToArray();
            MessageLength = BitConverter.ToInt32(sizeArray, 0);
            if (MessageLength < 0 || MessageLength > MaxMessageSize)
            {
                throw new Repo7CommandException(Messages.MessageLengthException);
            }
        }

        /// <summary>
        /// Write the bytes in the buffer to the memory stream
        /// </summary>
        /// <param name="offset">bytes offset</param>
        /// <param name="bytesRead">number of bytes received</param>
        public void WriteBytesToMemory(int offset, int bytesRead)
        {
            MemStream.Write(Buffer, offset, bytesRead - offset);
            BytesReceived += bytesRead - offset;
        }

        /// <summary>
        /// Check if the full message has been received
        /// </summary>
        /// <returns>true / false</returns>
        public bool IsMessageReceived()
        {
            if (BytesReceived < MessageLength)
            {
                return false;
            }
            else if (BytesReceived > MessageLength)
            {
                throw new Repo7CommandException(Messages.ReceivedBytesExceedMessageLenght);
            }
            return true;
        }

        /// <summary>
        /// De-serialize the command from the Buffer
        /// </summary>
        /// <returns></returns>
        public RepoCommand DeserializeCommandFromBuffer()
        {
            IFormatter formatter = new BinaryFormatter();
            MemStream.Position = 0;
            return formatter.Deserialize(MemStream) as RepoCommand;
        }

        #endregion
    }
}
